<?php

namespace app\common\service;

use app\admin\model\AppIp;
use app\admin\model\AppIpConfig;
use app\admin\model\AppIpListen;
use app\admin\model\AppIpReject;
use app\admin\model\SystemConfig;
use app\common\service\door\DoorException;
use think\facade\Cache;
use think\facade\Config;
use think\facade\Env;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Connection\AsyncUdpConnection;
use Workerman\Connection\TcpConnection;
use Workerman\Timer;
use Workerman\Worker;
use think\console\Output;
use think\facade\Db;
use think\facade\Log;
use Workerman\Connection\ConnectionInterface;

class DoorService
{

    protected $modelConfig = null;
    protected $listRules = [];

    /**
     * 输出实例
     *
     * @var Output
     */
    protected $output = null;

    public static $cacheKeyDoorDefaultRule = 'cacheKeyDoorDefaultRule';
    public static $cacheKeyDoorUnknowIpRule = 'cacheKeyDoorUnknowIpRule';
    public static $cacheKeyDisableIpList = 'cacheKeyDisableIpList';
    public static $cacheKeyAllowIpList = 'cacheKeyAllowIpList';
    public static $cacheKeyAllowIpRecordList = 'cacheKeyAllowIpRecordList';
    public static $cacheKeyDisableIpRecordList = 'cacheKeyDisableIpRecordList';

    public function __construct($client_uid, $output)
    {
        $this->modelConfig = AppIpConfig::where('uid', $client_uid)->where('status', 1)->find();

        if (empty($this->modelConfig)) {
            throw new DoorException('配置读取错误');
        }
        $this->output = $output;

        $this->initListRules();
    }

    public function recordContent($content, $level = 'debug')
    {
        if (!is_string($content)) {
            $content = print_r($content, true);
        }

        if ($level == 'debug' && $this->output->isDebug()) {
            $this->output->writeln($content);
        }
    }

    protected function initListRules()
    {
        $config_service = new IpConfigService($this->modelConfig);

        $list_rules = $config_service->buildPreviewMap();

        foreach ($list_rules as  $rules_item) {
            $listen_address = $this->initListenAddress($rules_item);

            $this->initRouteAddress($rules_item, $listen_address);
        }

        $this->listRules = array_filter($this->listRules);
    }

    protected function initListenAddress($item)
    {

        $protocol = $item['protocol'];
        $listen_ip = $item['listen_ip'];
        $listen_port = $item['listen_port'];

        $listen_address = "$protocol://$listen_ip:$listen_port";

        if (!isset($this->listRules[$listen_address])) {
            $this->listRules[$listen_address] = [];
        }

        return $listen_address;
    }

    protected function initRouteAddress($item, $listen_address)
    {

        $protocol = $item['protocol'];
        $route_ip = $item['route_ip'];
        $route_port = $item['route_port'];

        $route_address = "$protocol://$route_ip:$route_port";

        if (!isset($this->listRules[$listen_address][$route_address])) {
            $this->listRules[$listen_address][$route_address] = 0;
        }
    }

    public function returnRouteAddress($listen_address)
    {

        $list_route = $this->listRules[$listen_address];

        $return_address = null;

        if ($this->modelConfig->route_load_type == 'average') {
            arsort($list_route);
            reset($list_route);
            $return_address = key($list_route);
        } else if ($this->modelConfig->route_load_type == 'random') {
            $return_address = array_rand($list_route);
        }

        if (is_null($return_address)) {
            return null;
        }


        $this->listRules[$listen_address][$return_address]++;

        return $return_address;
    }

    public function validateIpAccessable($ip)
    {

        $this->recordContent('验证IP:' . $ip);

        $door_default_rule = Cache::get(DoorService::$cacheKeyDoorDefaultRule, 'whitelist_only');
        $door_unknow_ip_rule = Cache::get(DoorService::$cacheKeyDoorUnknowIpRule, 'deny');
        $list_allow_ip = Cache::get(DoorService::$cacheKeyAllowIpList, []);
        $list_disable_ip = Cache::get(DoorService::$cacheKeyDisableIpList, []);

        $this->recordContent("IP过滤规则：{$door_default_rule}");
        $this->recordContent("未知IP默认规则：{$door_unknow_ip_rule}");

        $this->recordContent("白名单：");
        $this->recordContent($list_allow_ip);

        $this->recordContent("黑名单：");
        $this->recordContent($list_disable_ip);

        if ($door_default_rule == 'whitelist_only') {
            $this->recordContent($list_allow_ip);
            if (in_array($ip, $list_allow_ip)) {
                return true;
            }
            return false;
        } else if ($door_default_rule == 'blacklist_only') {
            if (in_array($ip, $list_disable_ip)) {
                return false;
            }
            return true;
        } else if ($door_default_rule == 'allow_mixed') {
            if (in_array($ip, $list_allow_ip)) {
                return true;
            }
            if (in_array($ip, $list_disable_ip)) {
                return false;
            }

            if ($door_unknow_ip_rule == 'allow') {
                return true;
            } else if ($door_unknow_ip_rule == 'deny') {
                return false;
            } else if ($door_unknow_ip_rule == 'allow_record') {


                $list_allow_record_ip = Cache::get(DoorService::$cacheKeyAllowIpRecordList, []);

                if (!isset($list_allow_record_ip[$ip])) {
                    $list_allow_record_ip[] = $ip;
                    Cache::set(DoorService::$cacheKeyAllowIpRecordList, $list_allow_record_ip);
                }


                return true;
            } else if ($door_unknow_ip_rule == 'deny_record') {

                $list_disable_record_ip = Cache::get(DoorService::$cacheKeyDisableIpRecordList, []);

                if (!isset($list_disable_record_ip[$ip])) {
                    $list_disable_record_ip[] = $ip;
                    Cache::set(DoorService::$cacheKeyDisableIpRecordList, $list_disable_record_ip);
                }


                return false;
            }
        }

        return false;
    }


    public function initDoorWorker()
    {
        $door_service = $this;

        foreach ($this->listRules as $listen_address => $list_route) {

            $worker = new Worker($listen_address);
            $worker->name = 'door';
            $worker->count = Env::get('door.process_count', 8);

            $worker->onConnect = function ($connection) use ($listen_address, $door_service) {

                $remote_ip = $connection->getRemoteIp();

                $door_service->recordContent('on connect,remote ip:' . $remote_ip);


                if (!$door_service->validateIpAccessable($remote_ip)) {
                    $door_service->recordContent('拦截IP：' . $remote_ip);
                    AppIpReject::initRecord($remote_ip);
                    $connection->close();
                    return;
                }

                $door_service->recordContent('放行IP：' . $remote_ip);

                $to_address = $door_service->returnRouteAddress($listen_address);

                $door_service->recordContent('转发地址：' . $to_address);

                $to_connection = null;

                if (substr($to_address, 0, 3) == 'tcp') {
                    // 向TO发起连接
                    $to_connection = new AsyncTcpConnection($to_address);
                    $door_service->recordContent('发起TCP连接');
                } else {
                    $to_connection = new AsyncUdpConnection($to_address);
                    $door_service->recordContent('发起UDP连接');
                }


                $to_connection->onMessage = function ($source, $data) use ($connection, $remote_ip, $door_service) {
                    // 接收到来自TO的数据，返回的数据
                    $connection->send($data);
                    Cache::inc(md5($remote_ip) . '-to', strlen($data));
                };

                // 流程和流量控制
                $to_connection->onClose       = function ($source) use ($connection, $door_service) {
                    $door_service->recordContent('连接关闭');
                    $connection->close();
                };

                $connection->onBufferFull  = function ($dest) use ($to_connection) {
                    $to_connection->pauseRecv();
                };
                $connection->onBufferDrain = function ($dest) use ($to_connection) {
                    $to_connection->resumeRecv();
                };


                $connection->onMessage = function ($source, $data) use ($to_connection, $remote_ip, $door_service) {
                    // 接收来自IN的数据,请求的数据
                    $to_connection->send($data);
                    Cache::inc(md5($remote_ip) . '-in', strlen($data));
                };

                // 流程和流量控制
                $connection->onClose       = function ($source) use ($to_connection, $door_service) {
                    $door_service->recordContent('连接关闭');
                    $to_connection->close();
                };

                $to_connection->onBufferFull  = function ($dest) use ($connection) {
                    $connection->pauseRecv();
                };
                $to_connection->onBufferDrain = function ($dest) use ($connection) {
                    $connection->resumeRecv();
                };




                $to_connection->connect();
            };
        }
    }

    public function initReportWorker()
    {

        $worker_ip = new Worker();

        $worker_ip->name = 'report';

        $worker_ip->onWorkerStart = function () {
            Timer::add(5, function () {

                $list_allow_ip = AppIp::where('status', 1)
                    ->column('ip');

                Cache::set(DoorService::$cacheKeyAllowIpList, $list_allow_ip);

                $list_disable_ip = AppIp::where('status', 0)
                    ->column('ip');

                Cache::set(DoorService::$cacheKeyDisableIpList, $list_disable_ip);

                Cache::set(DoorService::$cacheKeyDoorDefaultRule, SystemConfig::where('name', 'door_default_rule')->where('group', 'door')->value('value'));

                Cache::set(DoorService::$cacheKeyDoorUnknowIpRule, SystemConfig::where('name', 'door_unknow_ip_rule')->where('group', 'door')->value('value'));

                $list_ip_map = array_merge($list_allow_ip, $list_disable_ip);

                foreach ($list_ip_map as  $ip) {
                    $ip_md5 = md5($ip);
                    $in_length = Cache::pull("$ip_md5-in");     // 请求的数据

                    $to_length = Cache::pull("$ip_md5-to");     // 返回的数据

                    if (!empty($in_length) || !empty($to_length)) {
                        $model_ip = AppIp::where('ip', $ip)->find();

                        $model_ip->in_buffer += $in_length;
                        $model_ip->to_buffer += $to_length;
                        $model_ip->save();
                    }
                }


                $list_allow_record_ip = Cache::get(DoorService::$cacheKeyAllowIpRecordList, []);

                foreach ($list_allow_record_ip as $ip) {
                    $model_ip = AppIp::where('ip', $ip)->find();

                    if (empty($model_ip)) {
                        $model_ip = new AppIp();
                        $model_ip->ip = $ip;
                        $model_ip->status = 1;
                        $model_ip->save();
                    }
                }

                Cache::set(DoorService::$cacheKeyAllowIpRecordList, []);


                $list_disable_record_ip = Cache::get(DoorService::$cacheKeyDisableIpRecordList, []);

                foreach ($list_disable_record_ip as $ip) {
                    $model_ip = AppIp::where('ip', $ip)->find();

                    if (empty($model_ip)) {
                        $model_ip = new AppIp();
                        $model_ip->ip = $ip;
                        $model_ip->status = 0;
                        $model_ip->save();
                    }
                }

                Cache::set(DoorService::$cacheKeyDisableIpRecordList, []);
            });
        };
    }
}
